// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    [ComVisible(true)]
    public class RulesEngineOperationsType : IRulesEngineOperationsType
    {
        readonly IDataPacketType m_Owner;

        public RulesEngineOperationsType(IDataPacketType owner)
        {
            m_Owner = owner;
        }

        public bool CONTAINS(object op1, object op2)
        {
            if (op1 == null || op2 == null)
                return false;
            string s1 = op1.ToString();
            string s2 = op2.ToString();
            return s1.IndexOf(s2, StringComparison.CurrentCultureIgnoreCase) > -1;
        }
        public bool CONTAINSHTML(object op1)
        {
            if (op1 == null)
                return true;
            if (!(op1 is string))
                throw new ArgumentException(string.Format("{0} cannot be used in a does not contain html expression", op1));
            const string regexString = "</?\\w+((\\s+\\w+(\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)+\\s*|\\s*)/?>";
            Regex rx = new Regex(regexString);
            return rx.Matches((string)op1).Count > 0;
        }
        public bool ENDSWITH(object op1, object op2)
        {
            if (op1 == null || op2 == null)
                return false;
            return op1.ToString().EndsWith(op2.ToString());
        }
        public bool LIKEMATCH(object op1, object op2, bool ignoreCase)
        {
            if (op1 == null || op2 == null)
                return false;
            Regex rx = new Regex(op2.ToString(), ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
            return rx.IsMatch(op1.ToString());
        }
        public bool LIKEMATCH(object op1, object op2)
        {
            return LIKEMATCH(op1, op2, false);
        }
        public bool EXACTMATCH(object op1, object op2, bool ignoreCase)
        {
            if (op1 == null || op1.ToString().Length == 0)  // empty string matches anything - hasvalue and required are a different matter
                return true;
            if (op2 == null)
                throw new ArgumentException("Invalid EXACTMATCH operator - the regular expression is missing");
            Regex rx = new Regex(op2.ToString(), ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
            MatchCollection matches = rx.Matches(op1.ToString());
            return matches.Count == 1 && string.Compare(matches[0].Value, op1.ToString(), ignoreCase) == 0;
        }
        public bool EXACTMATCH(object op1, object op2)
        {
            return EXACTMATCH(op1, op2, false);
        }
        public bool MAXLENGTHTEST(object op1, object op2)
        {
            if (op1 == null)
                return true;
            if (op2 == null)
                return false;
            int length;
            if (op2 is Int32 || op2 is Int16)
                length = Convert.ToInt32(op2);
            else
                if (!Int32.TryParse(op2.ToString(), out length))
                    throw new ArgumentException(string.Format("MAXLENGTHTEST requires an integer as its second parameter: {0} cannot be construed as such", op2));
            if (op1 is decimal || op1 is double || op1 is Single)
                return op1.ToString().Replace(".", "").Length <= length;
            return (op1.ToString()).Length <= length;
        }
        public bool MINLENGTHTEST(object op1, object op2)
        {
            if (op1 == null)
                return true;
            if (op2 == null)
                return false;
            int length;
            if (op2 is Int32 || op2 is Int16)
                length = Convert.ToInt32(op2);
            else
                if (!Int32.TryParse(op2.ToString(), out length))
                    throw new ArgumentException(string.Format("MINLENGTHTEST requires an integer as its second parameter: {0} cannot be construed as such", op2));
            if (op1 is decimal || op1 is double || op1 is Single)
                return op1.ToString().Replace(".", "").Length >= length;
            return (op1.ToString()).Length >= length;
        }
        public bool PRECISIONTEST(object op1, object op2)
        {
            if (op1 == null)
                return true;
            if (op2 == null)
                return false;
            int length;
            if (op2 is Int32 || op2 is Int16)
                length = Convert.ToInt32(op2);
            else
                if (!Int32.TryParse(op2.ToString(), out length))
                    throw new ArgumentException(string.Format("PRESCISIONTEST requires an integer as its second parameter: {0} cannot be construed as such", op2));
            decimal value;
            if (op1 is decimal)
                value = (decimal)op1;
            else
                if (!decimal.TryParse(op1.ToString(), out value))
                    throw new ArgumentException(string.Format("PRESCISIONTEST requires a decimal as its first parameter: {0} cannot be construed as such", op1));
            string s = value.ToString();
            int leftOfPoint = s.IndexOf(".");
            if (length < (s.Length - (leftOfPoint + 1)))
                return false;
            return true;
        }
        public bool STARTSWITH(object op1, object op2)
        {
            if (op1 == null || op2 == null)
                return false;
            return op1.ToString().StartsWith(op2.ToString());
        }
        public bool VALIDISBN(object op1)
        {
            if (op1 == null)
                return true;
            return ValidISBN(op1.ToString());
        }
        public bool VALIDUPC(object op1)
        {
            if (op1 == null)
                return true;
            return ValidUpc(op1.ToString());
        }
        public bool VALIDURI(object op1)
        {
            if (op1 == null)
                return true;
            return ValidUri(op1.ToString());
        }
        public bool HASAVALUE(object op1)
        {
            if (op1 == null)
                return false;
            if (op1 is System.Collections.IEnumerable)
                return ((System.Collections.IEnumerable)op1).GetEnumerator().MoveNext();
            return true;
        }
        public bool ALL(object op1, object op2)
        {
            if (op1 == null)
                return true;
            if (!(op2 is IBooleanValue))
                throw new ArgumentException(string.Format("The second parameter to ALL must be a Boolean expression: actual parameter was {0}", op2));
            if (!(op1 is System.Collections.IEnumerable))
                throw new ArgumentException(string.Format("The first parameter to ALL must be a list of some sort: actual parameter was {0}", op1));
            foreach (object valueObject in (System.Collections.IEnumerable)op1)
            {
                RulesEngineDataPacket data = new RulesEngineDataPacket();
                data.Bind("each", valueObject);
                if (!((IBooleanValue)op2).Value(data))
                    return false;
            }
            return true;
        }
        public bool SOME(object op1, object op2)
        {
            if (op1 == null)
                return true;
            if (!(op2 is IBooleanValue))
                throw new ArgumentException(string.Format("The second parameter to SOME must be a Boolean expression: actual parameter was {0}", op2));
            if (!(op1 is System.Collections.IEnumerable))
                throw new ArgumentException(string.Format("The first parameter to SOME must be a list of some sort: actual parameter was {0}", op1));
            foreach (object valueObject in (System.Collections.IEnumerable)op1)
            {
                RulesEngineDataPacket data = new RulesEngineDataPacket();
                data.Bind("each", valueObject);
                if (((IBooleanValue)op2).Value(data))
                    return true;
            }
            return false;
        }
        public void CREATEITEM(object itemType)
        {
            m_Owner.SetValue("CreateItem", itemType.ToString(), m_Owner);
        }
        public void ADDMESSAGE(object messageNumber, object organizationDomain, object severity, object message, object parameter1, object parameter2, object parameter3)
        {
            m_Owner.Logger((MajorEnum)severity, MinorEnum.Generic, message.ToString(), new[] { organizationDomain, parameter1, parameter2, parameter3 });
        }
        public void ADDITEMMESSAGE(object severity, object message)
        {
            m_Owner.Logger((MajorEnum)severity, MinorEnum.Generic, message.ToString(), new object[] { });
        }
        public void ADDPROPERTYMESSAGE(object severity, object message, object propertyName)
        {
            m_Owner.Logger((MajorEnum)severity, MinorEnum.Generic, message.ToString(), new[] { propertyName });
        }

        private static bool ValidISBN(string isbn)
        {
            try
            {
                Regex rx = new Regex("^(((978)|(979))([-]?[0-9]+){3})$|^(([-]?[0-9]+){4}([-]?X)?)$");
                if (isbn == null)
                    return true;
                if (rx.Match(isbn).Value != isbn)
                    return false;
                isbn = isbn.Replace(" ", "").Replace("-", "");
                if ((isbn.StartsWith("978") || isbn.StartsWith("979")) && isbn.Length == 13)
                {
                    if (isbn.Length != 13)
                        return false;
                    int cd = 0;
                    for (int i = 0; i < 12; i++)
                        cd += (isbn[i] - '0') * ((i % 2 == 0) ? 1 : 3);
                    cd = 10 - cd % 10;
                    cd = cd == 10 ? 0 : cd;
                    return isbn[isbn.Length - 1] == Convert.ToChar(cd + '0');
                }
                else
                {
                    if (isbn.Length != 10)
                        return false;
                    int cd = 0;
                    for (int i = 0; i < 9; i++)
                        cd += (isbn[i] - '0') * (10 - i);
                    cd = 11 - (cd % 11);
                    char cdc = cd == 10 ? 'X' : (cd == 11) ? '0' : Convert.ToChar(cd + '0');
                    return isbn[isbn.Length - 1] == cdc;
                }
            }
            catch (ArgumentNullException)
            {
                return false;
            }
        }

        private static bool ValidUpc(string rsUPC)
        {
            if (rsUPC == null)
            {
                return true;
            }

            string sUPC = CleanString(rsUPC).Trim();

            if (sUPC.Length == 0)
            {
                return false;
            }

            if (Regex.IsMatch(sUPC, @"NULL|NO\s*UPC|NOT\s*ISSUED|N\/A|N\\A|NIL|H2F|^CDW0", RegexOptions.IgnoreCase))
            {
                return false;
            }

            sUPC = Regex.Replace(sUPC, @"^#|-|\s|d$", string.Empty);

            if (sUPC.Length == 0)
            {
                return false;
            }

            // build a string of juse the first character of the UPC to see if they duplicated the same value for the entire string
            // 888888888888 or 000000000000
            if (string.Empty.PadLeft(sUPC.Length, sUPC.Substring(0, 1).ToCharArray()[0]).Equals(sUPC))
            {
                return false;
            }

            if (Regex.IsMatch(sUPC, @"(\d*)(\.)(\d*)"))
            {
                string[] aValues = sUPC.Split('.');
                if (aValues.Length > 0)
                {
                    sUPC = aValues[0];
                }
            }
            else if (!IsNumericValue(sUPC))
            {
                return false;
            }

            CheckDigits.UPCValidationEnum upcValid;

            // Deal with 10 digit UPCs  This usually means that the leading 0 is missing and the check digit
            if (sUPC.Length < 12)
            {

                if (sUPC.Length > 8 && sUPC.Length < 11)
                {

                    sUPC = sUPC.PadLeft(11, '0');

                }

                if (sUPC.Length == 11)
                {
                    upcValid = CheckDigits.ValidateUPC("0" + sUPC);
                    if (upcValid == CheckDigits.UPCValidationEnum.UPCChecksumInvalid)
                    {
                        sUPC += CheckDigits.GenerateUPCCheckDigit(sUPC);
                    }
                    else
                    {
                        sUPC = "0" + sUPC;
                    }

                }

            }
            else if (sUPC.Length > 12)
            {
                while (sUPC.Length > 12 && sUPC.StartsWith("0"))
                {
                    sUPC = sUPC.Substring(1);
                }
            }

            upcValid = CheckDigits.ValidateUPC(sUPC);

            if (upcValid == CheckDigits.UPCValidationEnum.UPCChecksumValid)
            {
                return true;
            }
            // Someone supports 13 digit UPCs in their system and only export the first 12 digits
            if (upcValid == CheckDigits.UPCValidationEnum.UPCChecksumInvalid && sUPC.Length == 12 && sUPC.StartsWith("0") && !(sUPC.StartsWith("01") && sUPC.StartsWith("08") && sUPC.StartsWith("09")))
            {
                return true;
            }
                // Books now have 13 digit UPCs, the typical ISBN is up convereted to having a 978 in front of the value and then a new check digit
                // Some data systems only support 12 digits so the leading 9 gets dropped in the process.
            if (upcValid == CheckDigits.UPCValidationEnum.UPCChecksumInvalid && sUPC.Length == 12 && (sUPC.StartsWith("78") || sUPC.StartsWith("79")))
            {
                if (CheckDigits.ValidateUPC("9" + sUPC) == CheckDigits.UPCValidationEnum.UPCChecksumValid)
                {
                    return true;
                }
            }
                // Some data systems only support 12 digits so the check digit gets dropped
            else if (upcValid == CheckDigits.UPCValidationEnum.UPCChecksumInvalid && sUPC.Length == 12 && (sUPC.StartsWith("978") || sUPC.StartsWith("979")))
            {
                return true;
            }

            return false;
        }

        static readonly Regex CleanStringRegex = new Regex(@"[\x00-\x08]|[\x0B-\x0C]|[\x0E-\x1F]");

        private static string CleanString(string rsString)
        {
            return CleanStringRegex.Replace(rsString, string.Empty);
        }

        private static bool IsNumericValue(object ruValue)
        {
            double retNum;
            bool isNum = Double.TryParse(Convert.ToString(ruValue), 
                System.Globalization.NumberStyles.Any, 
                System.Globalization.NumberFormatInfo.InvariantInfo, 
                out retNum);
            return isNum;
        }

        private class CheckDigits
        {
            public enum UPCValidationEnum
            {
                UPCChecksumInvalid = 0,
                UPCChecksumValid = 1,
                UPCLengthInvalid = 2
            }

            //public enum ISBNValidationEnum
            //{
            //    ISBNChecksumValid = 0,
            //    ISBNChecksumInvalid = 1,
            //    ISBNLengthInvalid = 2
            //}
            //public static ISBNValidationEnum ValidateISBN(string rsISBNNumber)
            //{
            //    if (rsISBNNumber.Length == 10)
            //    {
            //        if (rsISBNNumber.EndsWith(GenerateISBNCheckDigit(rsISBNNumber.Substring(0, 9))))
            //        {
            //            return ISBNValidationEnum.ISBNChecksumValid;
            //        }
            //        return ISBNValidationEnum.ISBNChecksumInvalid;
            //    }
            //    if (rsISBNNumber.Length == 13)
            //    {
            //        if (rsISBNNumber.EndsWith(GenerateUPCCheckDigit(rsISBNNumber.Substring(0, 12))))
            //        {
            //            return ISBNValidationEnum.ISBNChecksumValid;
            //        }
            //        return ISBNValidationEnum.ISBNChecksumInvalid;
            //    }
            //    return ISBNValidationEnum.ISBNLengthInvalid;
            //}

            //private static string GenerateISBNCheckDigit(string rsISBNNumber)
            //{
            //    int iSum = 0;
            //    int iI;
            //    int[] aDigits = { 10, 9, 8, 7, 6, 5, 4, 3, 2 };
            //    string[] aCheckDigits = { "0", "X", "9", "8", "7", "6", "5", "4", "3", "2", "1" };
            //    char[] aISBN = rsISBNNumber.ToCharArray();
            //    for (iI = 0; iI <= 8; iI++)
            //    {
            //        iSum += aDigits[iI] * Convert.ToInt32(aISBN[iI]);
            //    }
            //    return aCheckDigits[iSum % 11];
            //}

            public static UPCValidationEnum ValidateUPC(string rsUPC)
            {
                if (!(Regex.IsMatch(rsUPC, @"^\d+$")))
                {
                    return UPCValidationEnum.UPCChecksumInvalid;
                }
                if (rsUPC.Length == 8)
                {
                    string sTempUPCNumber = string.Empty;
                    int iFormulaDigit = Convert.ToInt32(rsUPC.Substring(6, 1));
                    if (iFormulaDigit == 0)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 3) + iFormulaDigit + "0000" + rsUPC.Substring(3, 3) + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 1)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 3) + iFormulaDigit + "0000" + rsUPC.Substring(3, 3) + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 2)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 3) + iFormulaDigit + "0000" + rsUPC.Substring(3, 3) + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 3)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 4) + "00000" + rsUPC.Substring(4, 2) + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 4)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 5) + "00000" + rsUPC.Substring(5, 1) + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 5)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 6) + "0000" + iFormulaDigit + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 6)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 6) + "0000" + iFormulaDigit + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 7)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 6) + "0000" + iFormulaDigit + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 8)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 6) + "0000" + iFormulaDigit + rsUPC.Substring(7, 1);
                    }
                    else if (iFormulaDigit == 9)
                    {
                        sTempUPCNumber = rsUPC.Substring(0, 6) + "0000" + iFormulaDigit + rsUPC.Substring(7, 1);
                    }
                    if (sTempUPCNumber.EndsWith(GenerateUPCCheckDigit(sTempUPCNumber.Substring(0, 11))))
                    {
                        return UPCValidationEnum.UPCChecksumValid;
                    }
                    return UPCValidationEnum.UPCChecksumInvalid;
                }
                if (rsUPC.Length >= 12 && rsUPC.Length <= 14)
                {
                    if (rsUPC.EndsWith(GenerateUPCCheckDigit(rsUPC.Substring(0, rsUPC.Length - 1))))
                    {
                        return UPCValidationEnum.UPCChecksumValid;
                    }
                    return UPCValidationEnum.UPCChecksumInvalid;
                }
                return UPCValidationEnum.UPCLengthInvalid;
            }

            public static string GenerateUPCCheckDigit(string rsUPC)
            {
                char[] aUpc = rsUPC.ToCharArray();
                int iUpcLength = rsUPC.Length + 1;
                int iSum = 0;
                int iI;

                if (iUpcLength == 12 || iUpcLength == 14)
                {
                    for (iI = 0; iI <= iUpcLength - 2; iI += 2)
                    {
                        iSum += int.Parse(aUpc[iI].ToString());
                    }
                    iSum *= 3;
                    for (iI = 1; iI <= iUpcLength - 2; iI += 2)
                    {
                        iSum += int.Parse(aUpc[iI].ToString());
                    }
                }
                else if (iUpcLength == 13)
                {
                    for (iI = 1; iI <= 11; iI += 2)
                    {
                        iSum += int.Parse(aUpc[iI].ToString());
                    }
                    iSum *= 3;
                    for (iI = 0; iI <= 11; iI += 2)
                    {
                        iSum += int.Parse(aUpc[iI].ToString());
                    }
                }
                int iMod = iSum % 10;
                if (iMod == 0)
                {
                    return Convert.ToString(0);
                }
                return Convert.ToString(10 - iMod);
            }
            /*
                    void TestISBNs()
                    {
                        ValidateISBN("1883012821").ToString();
                        ValidateISBN("0804215839").ToString();
                    }

                    void TestUPCs()
                    {
                        ValidateUPC("040421001927").ToString();
                        ValidateUPC("01289000").ToString();
                        ValidateUPC("01540400").ToString();
                        ValidateUPC("01540701").ToString();
                        ValidateUPC("01541302").ToString();
                        ValidateUPC("01541603").ToString();
                        ValidateUPC("03648254").ToString();
                        ValidateUPC("01541205").ToString();
                        ValidateUPC("01540206").ToString();
                        ValidateUPC("01540507").ToString();
                        ValidateUPC("01540808").ToString();
                        ValidateUPC("03784929").ToString();
                    }
            */
        }

        private static bool ValidUri(string uriString)
        {
            try
            {
                Uri uri = new Uri(uriString);
                return uri.IsWellFormedOriginalString();
            }
            catch (UriFormatException)
            {
                return false;
            }
            catch (ArgumentNullException)
            {
                return false;
            }
        }
    }

}
